package jf.collections;

import jf.closure.F;

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * @author ivanalx
 * @date 12.05.2009 15:38:18
 */
public abstract class Option<T> implements Iterable<T> {
	public static <T> Option<T> some(T t) {
		return new Some<T>(t);
	}

	public static <T> Option<T> none() {
		return new None<T>();
	}

	/**
	 * get the value of this option
	 */
	public abstract T get();

	/**
	 * True if the option is a None() false otherwise.
	 */
	public abstract boolean isEmpty();

	/**
	 * True if the option is a Some(..) false otherwise.
	 */
	public boolean isDefined() {
		return !isEmpty();
	}

	/**
	 * If the option is nonempty return its value, otherwise return the result of evaluating a default expression
	 *
	 * @param f closure for executing
	 */
	public T getOrElse(F<T> f) {
		if (isEmpty()) {
			return f.apply();
		} else {
			return get();
		}
	}

	/**
	 * If the option is nonempty return its value, otherwise return other value
	 */
	public T orElse(T other) {
		if (isEmpty()) {
			return other;
		} else {
			return get();
		}
	}

	/**
	 * A singleton list containing the option's value if it is nonempty or the empty list if the option is empty
	 */
	public List<T> toList() {
		return isEmpty() ? CollectionF.<T>asList() : CollectionF.asList(get());
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;

		Option option = (Option) o;
		if ((this.isDefined() && option.isEmpty()) || (this.isEmpty() && option.isDefined())) return false;
		if (this.isEmpty() && option.isEmpty()) return true;

		Object thisO = this.get();
		Object thatO = option.get();

		if (thisO != null ? !thisO.equals(thatO) : thatO != null) return false;
		return true;
	}

	public int hashCode() {
		if (isDefined()) {
			T t = this.get();
			if (t == null) {
				return Integer.MAX_VALUE;
			} else {
				return t.hashCode();
			}
		} else {
			return 0;
		}
	}

	public abstract String toString();


	/**
	 * Iterate this option
	 */
	public Iterator<T> iterator() {
		return new Iterator<T>() {
			boolean isOver = isEmpty();

			public boolean hasNext() {
				return !isOver;
			}

			public T next() {
				if (isOver) {
					throw new NoSuchElementException();
				} else {
					isOver = true;
					return get();
				}
			}

			public void remove() {
				throw new RuntimeException("Not supported");
			}
		};
	}

	private static final class Some<T> extends Option<T> {
		private T object;

		public Some(T object) {
			this.object = object;
		}

		public T get() {
			return object;
		}

		public boolean isEmpty() {
			return false;
		}

		@Override
		public String toString() {
			return "Option: Some = [" + object + "]";
		}
	}

	private static final class None<T> extends Option<T> {
		public T get() {
			throw new NoSuchElementException();
		}

		public boolean isEmpty() {
			return true;
		}

		@Override
		public String toString() {
			return "Option: None";
		}
	}
}
